Introduction

This notebook documents my analysis on Unit Investment Trust Funds. The motivation for this is that I’m looking to invest again in UITFs. What’s good about UITFs is that you can have a diversified investment with low capital and at the convenience of having professional portfolio managers handle these funds for you. This time however, I plan to be more knowledgeable before I dive in so I can know all my options and plan on a good enough strategy to meet my investment objectives.

Specific questions in mind for this analysis are the following:

  1. What are the available UITFs in the Philippines? Which banks offer them?
  2. What are the different classes of UITFs? How do they differ from one another?
  3. Which can be considered the best if I’m aiming for good returns from my investment? Or if I want to preserve my capital?

Also note that this is an R markdown notebook. Feel free to expand the code chunks if you’re interested in looking at the code used for this analysis.


Data Gathering & Preparation

This section describes the data gathering and processing steps. You can skip to the next section to start reading about the actual analysis. Below are the three raw data sets required for this analysis. The data gathering and preprocessing steps are described after.

Raw Data Description
1 Complete UITF List Complete list of the names and ticker symbols of all available UITFs in the Philippines + some basic info
2 Daily Price Data Daily price data for the the past year for each UITF
3 Fund Information All other important information including fund and risk classifications, etc. for each UITF


These 3 raw data sets will then be transformed into our pair of working data sets which are:

Processed Data Description
1 UITF Matrix Comprehensive data frame containing all important information on all available UITFs
2 Daily Percent Changes/Returns Daily price data represented as percent changes relative to yesterday’s price and relative to earliest date in our 1-year data
# clear workspace
rm(list = ls())

# load R packages
library(dplyr)          # for data frame wrangling
library(readr)          # for reading and writing flat files
library(stringr)        # for string manipulation
library(lubridate)      # for date string parsing
library(purrr)          # for tidy functional programming
library(tidyr)          # for data frame reshaping
library(rjson)          # for parsing json files
library(Hmisc)          # for string cleaning

library(ggplot2)        # for most of the graphs
library(ggiraph)        # for adding interactive tooltips to ggplot graphs
library(RColorBrewer)   # for custom plotting colors
library(DT)             # for outputting interactive data tables
library(rmarkdown)      # for outputting html paged tables

library(rvest)          # for parsing html tables
library(XML)            # for parsing html
library(RCurl)          # for parsing html

Raw Data: UITF List

Pulling all PH ticker symbols from Bloomberg

For our first data set, we’ll pull a list of all available UITFs from Bloomberg.com. This is done using Bloomberg’s symbol lookup tool. We’re going to do a basic search for all ticker symbols for all Philippines-based stocks and products. From this set, we’ll filter in only those symbols for UITFs, our product of interest.

# scrape PH symbols from bloomberg.com
if(!file.exists("./Datasets/all_symbols.csv")) {
    # query bloomberg symbol lookup: "PH"
    url <- "https://www.bloomberg.com/markets/symbolsearch?query=PH"
    
    # initialize results table
    all_symbols <- data_frame(Symbol = character(),
                              Name = character(),
                              Country = character(),
                              Type = character(),
                              `Industry/Objective` = character())
    
    # scrape all pages of query results
    for(i in 1:199) {
        result <- read_html(paste0(url, "&page=", i)) %>% 
            html_nodes(xpath='//*[@class="dual_border_data_table alt_rows_stat_table tabular_table"]') %>% 
            html_table()
        
        all_symbols <- bind_rows(all_symbols, result)
    }
    
    # save results to local folder
    write_csv(all_symbols, "./Datasets/all_symbols.csv")
    rm(all_symbols, url, result)
}

# load to memory
all_symbols <- read_csv("./Datasets/all_symbols.csv") %>% 
    select(Symbol, Name, Country, Type)

# show first 5 rows in document
paged_table(head(all_symbols, n=3))


We will do a quick filter for just PH open-end fund type symbols. Next is we need to remove other fund types from this data set such as mutual funds and VULs since we’re only interested in UITFs. For this, we need need to gather info from each symbol’s quote page from Bloomberg.com/quote/ such as fund profile, inception date, currency, etc. We can access some of the information in this quote page in JSON format using this web address: https://www.bloomberg.com/markets/api/quote-page/ + <ticker symbol>

# gather basic info for each symbol
if(!file.exists("./Datasets/symbol_info.csv")) {
    
    # filter in PH open-end funds only
    symbol_info <- all_symbols %>% 
        filter(Country == "PH", Type == "Open-End Fund") %>% 
        select(Symbol, Name) %>% 
        mutate(Profile = "", 
               Website = "",
               `Fund Type` = "",
               `Inception Date` = "",
               Currency = "")
    
    # scrape profile and other info for each symbol
    for(i in 1:nrow(symbol_info)) {
        print(i)
        
        # construct url for json data
        url <- paste0("https://www.bloomberg.com/markets/api/quote-page/",
                      filtered_symbols$Symbol[i])
        
        # scrape json data
        json_data <- fromJSON(file = url) %>% 
            unlist() %>% 
            data_frame(key = names(.), value = .)
        
        # define function for extracting specific values from json data
        find_value <- function(keyword, df) {
            ifelse(
                !any(str_detect(json_data$key, keyword)), 
                NA, 
                df %>% 
                    filter(str_detect(key, keyword)) %>% 
                    slice(1) %>% 
                    `[[`("value")
            )
        }
        
        # assign values to data frame
        symbol_info$Profile[i] <- find_value("profile\\.description", json_data)
        symbol_info$Website[i] <- find_value("profile\\.website", json_data)
        symbol_info$`Fund Type`[i] <- find_value("fundType", json_data)
        symbol_info$`Inception Date`[i] <- find_value("inceptionDate", json_data)
        symbol_info$Currency[i] <- find_value("issuedCurrency", json_data)
        
        # clean up workspace
        rm(url, json_data, find_value)
    }
    
    # save to local folder
    write_csv(symbol_info, "./Datasets/symbol_info.csv")
    rm(i, symbol_info)
}

symbol_info <- read_csv("./Datasets/symbol_info.csv")

# fill in missing values for Fund Type column
for(i in which(is.na(symbol_info$`Fund Type`))) {
    symbol_info$`Fund Type`[i] <- if(str_detect(tolower(symbol_info$Profile[i]), "trust")) {
        "Unit Trust"
    } else {
        NA
    }
}
rm(i, all_symbols)

Complete UITF List

All these additional data are gathered to screen out all non-UITF symbols from our list and also for mapping them to their fund and risk classifications later. You can view our clean data below.

# filter in UITFs only
all_UITFs <- symbol_info %>% 
    filter(`Fund Type` == "Unit Trust") %>% 
    mutate(Website = str_replace(Website, "http:", "")) %>% 
    mutate(Website = str_replace_all(Website, "/", ""))

# fill in missing values for Website column
all_UITFs$Website[is.na(all_UITFs$Website) & str_detect(all_UITFs$Profile, "Rizal")] <- "www.rcbc.com"

# manually define bank-website mapping ala dictionary
bank_websites <- list(`AB Capital` = "www.abcapitalonline.com",
                      ATRAM = "www.atram.com.ph",
                      `Asia United Bank` = "www.aub.com.ph",
                      `Bank of Commerce` = "www.bankcom.com.ph",
                      BDO = "www.bdo.com.ph",
                      BPI = "www.bpiexpressonline.com",
                      `China Bank` = "www.chinabank.ph",
                      EastWest = "www.eastwestbanker.com",
                      MetroBank = "www.metrobank.com.ph",
                      PBCOM = "www.pbcom.com.ph",
                      PNB = "www.pnb.com.ph",
                      RCBC = "www.rcbc.com",
                      `Security Bank` = "www.securitybank.com.ph",
                      UCPB = "www.ucpb.com",
                      `Union Bank` = "www.unionbankph.com") %>% 
    data_frame(Bank = names(.), Website = .) %>% 
    mutate(Website = as.character(Website))

# clean fund name function
clean_fund_name <- function(fund_name) {
    fund_name %>% 
    str_split(" ") %>% 
        unlist() %>% 
        map_chr(
            function(x){
                ifelse(
                    x %in% c("AB", "ATRAM", "AUB", "U.S.", "US", "ABF", "CBC", "GS", "II", "III", "PNB", "SB", "UCPB", "US$", "PSEI", "PSEi", "BDO", "BPI", "CTBC", "RCBC", "BOC", "UBP", "UITF"),
                    x, capitalize(tolower(x))
                )
            }
        ) %>% 
        paste(collapse = " ") %>% 
        str_trim()
}

# merge bank info
all_UITFs <- all_UITFs %>% 
    left_join(bank_websites) %>% 
    select(Symbol, Name, Bank, Currency, `Inception Date`) %>% 
    mutate(Name = map_chr(Name, clean_fund_name)) %>% 
    unique()

# clean up
rm(bank_websites, symbol_info)

paged_table(head(all_UITFs, n = 3))


Raw Data: Daily Price Data

Now that we have a complete list of UITFs, we can pull daily price data for each one for the past year. We’ll get this data from Bloomberg using this simple hack which exposes the raw data used in their graphs. We just have to access their site as follows: www.bloomberg.com/markets/chart/data/1Y/ + <ticker symbol>. This returns the raw data in JSON format. See example below for BPIEQUI:PM.

We merge all data for all UITFs in a long data frame and then save to a local folder again. At this point we’re more concerned with collecting data first so this format ensures we don’t lose any data during collection. Later on we will reshape this data frame as needed in our subsequent analyses.

# initialize price data frame
price_data <- data_frame(Date = as.Date(vector()),
                         NAVPU = double(),
                         Symbol = character())

# extract data from web
if(!file.exists("./Datasets/uitf_daily_prices_raw.csv")) {
    for(i in 1:nrow(all_UITFs)) {
    
        # progress tracker
        print(paste0("Extracting data | ", i, " out of ", nrow(all_UITFs)))
    
        # build url
        url <- paste0("https://www.bloomberg.com/markets/chart/data/1Y/", all_UITFs$Symbol[i])
    
        # download raw JSON data
        json_data <- fromJSON(file= url)[["data_values"]]
        if(length(json_data) == 0) next()
    
        # transform to data frame
        uitf_daily_prices <- json_data %>% 
            unlist() %>% 
            matrix(ncol = 2, byrow = TRUE, 
                   dimnames = list(c(), c("Date", "NAVPU"))) %>% 
            as_data_frame()  %>% 
            mutate(Date = as.Date(as.POSIXlt(Date/1000, origin = "1970-01-01"))) %>%
            mutate(Symbol = all_UITFs$Symbol[i])
    
        # merge to price data
        price_data <- bind_rows(price_data, uitf_daily_prices)
    }

    # save data to local folder
    write_csv(price_data, "Datasets/uitf_daily_prices_raw.csv")
    rm(i, url, json_data, uitf_daily_prices, price_data)
}

# load data
price_data <- read_csv("./Datasets/uitf_daily_prices_raw.csv") %>% 
    unique()

# show some data
print(price_data)
## # A tibble: 28,486 Ă— 3
##          Date  NAVPU     Symbol
##        <date>  <dbl>      <chr>
## 1  2016-05-02 1.1280 PNBMHPR:PM
## 2  2016-05-03 1.1281 PNBMHPR:PM
## 3  2016-05-04 1.1281 PNBMHPR:PM
## 4  2016-05-05 1.1282 PNBMHPR:PM
## 5  2016-05-06 1.1282 PNBMHPR:PM
## 6  2016-05-10 1.1284 PNBMHPR:PM
## 7  2016-05-11 1.1285 PNBMHPR:PM
## 8  2016-05-12 1.1285 PNBMHPR:PM
## 9  2016-05-13 1.1286 PNBMHPR:PM
## 10 2016-05-16 1.1287 PNBMHPR:PM
## # ... with 28,476 more rows


Raw Data: Fund Information

For our last raw data set, we’ll pull basic information about the available UITFs in the Philippines from uitf.com. We’ll use the rvest package to parse tables in web pages then save it as CSV to our local folder. Take a glimpse at a portion of the data we just extracted.

# parse and save data in to local folder
if(!file.exists("./Datasets/fund_info.csv")) {
    url <- "http://www.uitf.com.ph/print_matrix.php?sort=&sortby=bank&sortorder=asc&class_id=&currency="
    read_html(url) %>% 
        html_nodes(xpath='//*[@class="hovertable"]') %>% 
        html_table() %>% 
        `[[`(1) %>% 
        write_csv("Datasets/fund_info.csv")
}

# manually define bank name mappings ala dictionary
bank_names <- list(`AB Capital` = "AB Capital",
                    ATRAM = "ATRAM Trust Corporation",
                    `Asia United Bank` = "Asia United Bank",
                    `Bank of Commerce` = "Bank of Commerce",
                    BDO = "BDO Unibank, Inc.",
                    BPI = "BPI Asset Management and Trust Corporation",
                    `China Bank` = "China Banking Corporation",
                    `CTBC Bank` = "CTBC Bank (Philippines) Corp.",
                    DBP = "Development Bank of the Philippines",
                    EastWest = "EastWest Banking Corporation",
                    LandBank = "LandBank of the Philippines",
                    MetroBank = "Metropolitan Bank & Trust Co.",
                    PBCOM = "Philippine Bank of Communications",
                    `Phil. Business Bank` = "Philippine Business Bank",
                    PNB = "Philippine National Bank",
                    PSBank = "Philippine Savings Bank",
                    `RCBC Savings` = "RCBC Savings",
                    RCBC = "Rizal Commercial Banking Corporation",
                    `Robinsons Bank` = "Robinsons Bank",
                    `Security Bank` = "Security Bank Corporation",
                   `Sterling Bank of Asia` = "Sterling Bank of Asia",
                    UCPB = "United Coconut Planters Bank",
                    `Union Bank` = "Union Bank") %>% 
    data_frame(Bank = names(.), `Bank Name` = .) %>% 
    mutate(`Bank Name` = as.character(`Bank Name`))

# load data into memory then clean data
fund_info <- read_csv("./Datasets/fund_info.csv", col_types = "ccccccnnncccccccn") %>% 
        mutate(`Inception Date` = mdy(`Inception Date`), `Last Uploaded Date` = mdy(`Last Uploaded Date`)) %>% 
    mutate(`Fund Name` = str_replace(`Fund Name`, "\\(.*\\)", "")) %>% 
    select(`Bank Name` = Bank, 
           `Fund Name`, 
           `Fund Classification` = Classification,
           `Risk Classification`,
           `Inception Date`,
           Currency,
           `Min. Initial Participation`,
           `Min. Additional Participation`,
           `Min. Holding Period`,
           `Trust Fee Structure`,
           `Early Redemption Fee`,
           Benchmark,
           `Last Uploaded Date`,
           `Latest NAVpu`) %>% 
    left_join(bank_names) %>% 
    select(Bank, everything(), -`Bank Name`) %>% 
    mutate(`Fund Classification` = str_replace(`Fund Classification`, "Money - Market", "Money Market")) %>% 
    mutate(`Fund Name` = map_chr(`Fund Name`, function(x){ clean_fund_name(x) }))

# clean up
rm(bank_names, i, clean_fund_name)

# show a snippet of our data frame
fund_info %>% 
    select(`Bank`, `Fund Name`, `Fund Classification`, `Risk Classification`, everything()) %>% 
    head(n = 3) %>% 
    paged_table()


Processed Data: UITF Matrix

So far we have two tables which have various info on UITFs. First is the UITF List which contains all UITF names and their ticker symbols which is our link to our last data set, the price data. Second is the Fund Info table which contains all other relevant information such as fund and risk classifications among others. For our working data set, we need to merge these two tables together.

Each table has a fund name for each UITF. However, the fund names are not standard between the two sources, UITF.com and Bloomberg.com. We have lots of other information for each table so we’re going to use these cleverly to be able to merge these two tables. Expand the code chunk below to see how this was done.

# match UITFs with exact same names between tables
fund_info_labelled <- fund_info %>% 
    filter(Bank %in% all_UITFs$Bank) %>% 
    mutate(string_to_match = tolower(`Fund Name`)) %>% 
    left_join(transmute(all_UITFs, string_to_match = tolower(Name), Symbol = Symbol, `Match Name` = Name)) %>% 
    select(-string_to_match)

# label UITFs with same bank and inception date
for(i in which(is.na(fund_info_labelled$Symbol))) {
    match <- all_UITFs %>% 
        filter(Bank == fund_info_labelled$Bank[i]) %>% 
        filter(Currency == fund_info_labelled$Currency[i]) %>% 
        filter(`Inception Date` == fund_info_labelled$`Inception Date`[i]) %>% 
        `[`(c("Symbol", "Name"))
    
    if(nrow(match) == 1 && !(match$Symbol %in% fund_info_labelled$Symbol)) {
        fund_info_labelled$Symbol[i] <- match$Symbol
        fund_info_labelled$`Match Name`[i] <- match$Name
    }
    
    rm(match)
}

# label UITFs with exact same Bank, currency, and NAVPU at latest date
temp_price_data <- price_data %>% 
    left_join(all_UITFs[, c("Symbol", "Name", "Bank", "Currency")]) %>% 
    unique()

for(i in which(is.na(fund_info_labelled$Symbol))) {
    
    ref_price <- fund_info_labelled$`Latest NAVpu`[i]
    n_digits <- (ref_price - floor(ref_price)) %>% 
        sprintf("%f", .) %>% 
        str_replace("0\\.", "") %>% 
        str_replace("0*$", "") %>% 
        nchar()
    
    match <- temp_price_data %>% 
        filter(Bank == fund_info_labelled$Bank[i]) %>% 
        filter(Currency == fund_info_labelled$Currency[i]) %>% 
        filter(Date == fund_info_labelled$`Last Uploaded Date`[i]) %>% 
        filter(floor(NAVPU*10^n_digits) == ceiling(ref_price*10^n_digits)) %>% 
        `[`(c("Symbol", "Name"))
    
    if(nrow(match) == 1 && !(match$Symbol %in% fund_info_labelled$Symbol)) {
        fund_info_labelled$Symbol[i] <- match$Symbol
        fund_info_labelled$`Match Name`[i] <- match$Name
    }
    
    rm(match, ref_price, n_digits)
}

# label UITFs with exact same Bank, currency, and approximately the same NAVPU at latest date
for(i in which(is.na(fund_info_labelled$Symbol))) {
    
    ref_price <- fund_info_labelled$`Latest NAVpu`[i]
    
    match <- temp_price_data %>% 
        filter(Bank == fund_info_labelled$Bank[i]) %>% 
        filter(Currency == fund_info_labelled$Currency[i]) %>% 
        filter(Date == fund_info_labelled$`Last Uploaded Date`[i]) %>% 
        mutate(price_error = abs(ref_price - NAVPU)) %>% 
        filter(price_error < 0.01) %>% 
        `[`(c("Symbol", "Name", "price_error"))
    
    
    if(nrow(match) >= 1) {
        if(nrow(match) > 1) {
            match <- match %>% 
                arrange(price_error) %>% 
                slice(1)
        }
        
        if(!(match$Symbol %in% fund_info_labelled$Symbol)) {
            fund_info_labelled$Symbol[i] <- match$Symbol
            fund_info_labelled$`Match Name`[i] <- match$Name
        }
        
    }
    
    rm(match, ref_price, n_digits)
}

# select columns
fund_info_labelled <- fund_info_labelled %>% 
    select(Symbol, everything(), -Bank, -`Inception Date`, -Currency, -`Last Uploaded Date`, -`Latest NAVpu`, -`Match Name`)

# merge the two tables
UITF_matrix <- left_join(all_UITFs, fund_info_labelled, by = "Symbol") %>% 
    mutate(`Risk Classification` = map_chr(`Risk Classification`, function(x){ ifelse(is.na(x), "Unknown", x) })) %>% 
    mutate(`Risk Classification` = factor(`Risk Classification`, levels=c("Aggressive", "Moderate", "Conservative"))) %>% 
    filter(!is.na(`Risk Classification`))


# clean up workspace
rm(fund_info_labelled, temp_price_data, i)

# show head of UITF_matrix
paged_table(head(UITF_matrix, n=3))


Processed Data: Percent Change & Return

Quick look at our data

We can have a quick look at our data using a trend chart. Since the prices are on relatively different ranges, this graph does not provide much insight.

# sample 30 UITFs to show in trend chart
set.seed(10)
sample_UITFs <- sample(price_data$Symbol, 30)

# plot trend chart
plot <- price_data %>%
    filter(Symbol %in% sample_UITFs) %>% 
    ggplot(aes(x=Date, y=NAVPU, col=Symbol, group=Symbol, tooltip=Symbol)) +
    geom_line_interactive(lwd=1, alpha=0.8) +
    labs(x = "",
         y = "Net Asset Value per Unit",
         title = "Daily NAVPUs from 2016/04/26 to 2017/04/25") +
    theme(legend.position = "none",
          plot.title = element_text(hjust = 0.5))

ggiraph(ggobj = plot, height_svg=5, width_svg = 9, width = 1, tooltip_opacity=0.8)


Calculating percent changes and returns

To compare these UITFs, the raw daily price data must be converted in terms of percent return relative to the earliest price in our data for each UITF (Apr 26, 2016). Take a look at the same subset of our price data represented as %returns.

# calculate %returns
returns_data <- price_data %>% 
    select(Symbol, Date, NAVPU) %>% 
    group_by(Symbol) %>% 
    arrange(Symbol, Date) %>% 
    mutate(Change = ((NAVPU/lag(NAVPU))-1)*100) %>% 
    mutate(Return = ((NAVPU/first(NAVPU))-1)*100) %>% 
    ungroup()

# plot all UITF trends
plot <- returns_data %>% 
    filter(Symbol %in% sample_UITFs) %>% 
    ggplot(aes(x=Date, y=Return, col=Symbol, group=Symbol, tooltip=Symbol)) +
    geom_line_interactive(lwd=1, alpha=0.8) +
    labs(x = "",
         y = "Returns (%)",
         title = "Overlay of % Return Trends") +
    theme(legend.position = "none",
          plot.title = element_text(hjust = 0.5))

# clean up workspace
rm(sample_UITFs)

ggiraph(ggobj=plot, height_svg=5, width_svg=9, width=1, tooltip_opacity=0.8)


Exploring UITFs

Timeline

The first UITFs in the country were established in 1994 by BPI. Interestingly, year 2005 has seen the largest number of UITF introductions from multiple banks followed by a number of product launches for the next two years. Each year since 2013 then, the number of UITFs in the country is slowly but constantly growing through numerous fund introductions.

# initialize custom categorical colors
my_qual_palette <- function(n) {
    all_qual_palettes <- brewer.pal.info %>%
        mutate(Palette = rownames(.)) %>%
        filter(category == "qual") %>% 
        filter(!(Palette %in% c("Set3", "Pastel2", "Pastel1"))) %>%
        select(Palette, maxcolors)

    map2(all_qual_palettes$maxcolors,
                                all_qual_palettes$Palette,
                                brewer.pal) %>% 
        unlist() %>% 
        `[`(!str_detect(., "FFFF")) %>%
        `[`(c(1:n))
}

# dot plot
plot <- UITF_matrix %>% 
    select(Bank, Name, `Inception Date`) %>% 
    mutate(Year = year(`Inception Date`)) %>% 
    mutate(Bank_full_name = Bank) %>% 
    mutate(tooltip = paste(paste0("Name:\t", Name), 
                           paste0("Bank:\t", Bank_full_name), 
                           paste0("Inception Date:\t", `Inception Date`), 
                           sep="\n")) %>% 
    arrange(Bank, Name) %>% 
    group_by(Year) %>%
    mutate(Rank = 1:n()) %>% 
    mutate(Rank = Rank - mean(Rank)) %>% 
    ggplot(aes(x = Year, y = Rank, col = Bank)) +
    geom_point_interactive(aes(tooltip = tooltip, data_id = 1), size = 3) +
    coord_cartesian(xlim = c(1994, 2018)) +
    scale_x_continuous(breaks = seq(1990,2020, by = 2)) +
    scale_y_continuous(breaks = NULL) +
    labs(x = "Year", y = "", title = "Timeline of UITF Inceptions") +
    theme(legend.text = element_text(size = 9),
          legend.key.size = unit(5, "mm"),
          legend.position = "right",
          plot.title = element_text(hjust = 0.5),
          axis.text.x = element_text(vjust = 0.5, angle = 0),
          panel.background = element_rect(fill = "grey95")
          ) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    scale_color_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank))))

ggiraph(ggobj = plot, height_svg=5, width_svg = 9, width = 1, 
        hover_css = "stroke:brown; stroke-width:1pt; fill:red")


Bank Portfolios

Overall, there are 122 active UITFs in the Philippines. The graph below shows BPI having the largest portfolio with 20 different products followed by Security Bank with 7 and BDO with 17. All banks with multiple UITF products have both peso-denominated and U.S. dollar-denominated funds.

plot_data <- UITF_matrix %>% 
    select(Bank, Currency, Product = Name) %>% 
    mutate(Product = paste("-", Product)) %>% 
    arrange(Product) %>% 
    group_by(Bank) %>% 
    mutate(Total = n()) %>% 
    ungroup() %>% 
    group_by(Bank, Currency) %>% 
    mutate(Count = n()) %>%
    mutate(Products = paste(Product, collapse = "\n")) %>% 
    select(-Product) %>% 
    mutate(tooltip = paste(Bank,
                           paste(Count, "out of", Total , "products"),
                           Products,
                           sep = "\n")) %>% 
    ungroup() %>% 
    unique() %>% 
    mutate(Currency = factor(Currency, levels = c("USD", "PHP"))) 

plot <- plot_data %>% 
    ggplot(aes(x = reorder(Bank, desc(Total)), 
               y = Count,
               fill = Currency, 
               tooltip = tooltip)) +
    geom_bar_interactive(aes(data_id = "any"), stat = "identity") +
    geom_text(data = unique(plot_data),
              aes(label = Total, y = Total),
              nudge_y = 1.2, alpha=0.8) +
    theme(plot.title = element_text(hjust = 0.5, size = 12),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.text.x = element_text(hjust=1, vjust=0.5, angle=90),
          axis.ticks.x = element_blank(),
          panel.grid = element_blank(),
          panel.background = element_rect(fill = "grey98"),
          legend.position = c(0.94, 0.85),
          legend.direction = "vertical",
          legend.background = element_rect(color = "grey50")
    ) +
    labs(title = "Portfolio Size per Bank",
         x = "",
         y = "") +
    scale_fill_manual(values = c("forestgreen", "cornflowerblue")) +
    scale_y_continuous(limits = c(0,24), expand = c(0,0))
    
ggiraph(ggobj = plot, height_svg=5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.7")


Starting Capital

One known advantage of UITFs is the low initial capital required to start investing. Typically advertised starting capital is P10,000. Looking at our data set, the median starting capital required is indeed P10,000.

Looking at the entire range shows a better finding, some UITFs accept lower starting capitals from P5,000 down to P1,000. This means some UITFs are more affordable than the typical notion. On the other side of the spectrum are a couple of funds with minimum starting capital of a whopping P5,000,000. Looking at the individual profiles of these funds online shows that these funds are intended for corporate clients which explains the staggering amount.

Meanwhile, for USD-denominated funds, the typical starting capital required is $1,000 but can be as low as $100 and as high as $5,000 for other funds.

plot <- UITF_matrix %>% 
    select(Bank, Name, Currency, Initial = `Min. Initial Participation`) %>% 
    arrange(Initial) %>% 
    filter(Initial > 0) %>%
    mutate(Initial_labels = as.factor(formatC(as.integer(Initial), big.mark=","))) %>%
    mutate(Initial_scale = as.integer(Initial_labels)) %>% 
    group_by(Currency, Initial) %>%
    arrange(Bank) %>% 
    mutate(Rank = 1:n()) %>% 
    mutate(tooltip = paste(paste(Name),
                           paste("Bank:", Bank),
                           paste("Amount:", Currency, Initial_labels),
                           sep = "\n")) %>% 
    ggplot(aes(y = reorder(Initial_labels, Initial), 
               x = Rank, col = Bank)) +
    geom_point_interactive(aes(tooltip = tooltip, data_id = 1), 
                           size = 3.5) +
    facet_grid(Currency ~ ., scales = "free_y", space = "free_y", switch = "y") +
    labs(title = "Minimum Initial Participation",
         y = "Amount") +
    theme(legend.position = "right",
          plot.title = element_text(hjust = 0.5),
          axis.title.x = element_blank(),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          panel.grid.major.x = element_blank(),
          panel.grid.minor.x = element_blank(),
          panel.background = element_rect(fill = "grey95")) + 
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    scale_color_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank))))

ggiraph(ggobj = plot, height_svg=4.2, width_svg = 9, width = 1, 
        hover_css = "stroke:brown; stroke-width:1pt; fill:red")


Topping up your investments

Aside from minimum initial participation, each UITF also specifies a minimum additional participation amount. This means that if you wish to add to your investment for a specific UITF, you must do so in chunks not less than this minimum amount. Typically, this minimum additional amount is lower than the initial amount required.

plot <- UITF_matrix %>% 
    select(Symbol, Bank, Name, Currency, 
           Initial = `Min. Initial Participation`,
           Additional = `Min. Additional Participation`) %>% 
    filter(Initial > 0) %>% 
    mutate(Currency = factor(Currency, levels = c("USD", "PHP"))) %>%
    mutate(tooltip = paste(Name,
                           paste("Bank:", Bank),
                           paste("Initial:", Currency, 
                                 formatC(as.integer(Initial), 
                                         big.mark = ",", 
                                         format = "f",
                                         digits = 2)),
                           paste("Additional:", Currency,
                                 formatC(as.integer(Additional), 
                                         big.mark = ",",
                                         format = "f",
                                         digits = 2)),
                           sep = "\n")) %>% 
    gather(amount_type, amount, -Symbol, -Bank, -Name, -Currency, -tooltip) %>% 
    mutate(amount_labels = as.factor(formatC(as.integer(amount), big.mark=","))) %>%
    mutate(amount_type = factor(amount_type, levels = c("Initial", "Additional"))) %>% 
    group_by(amount_labels, amount_type) %>%
    arrange(Currency) %>% 
    mutate(x_position = 1:n()) %>% 
    mutate(x_position = x_position - mean(x_position)) %>%
    mutate(x_position = x_position/diff(range(x_position))*n()/20) %>% 
    mutate(x_position = x_position + 10*(as.integer(amount_type)-1)) %>% 
    ungroup() %>% 
    ggplot(aes(x = x_position, 
               y = reorder(amount_labels, amount),
               col = Currency,
               data_id = "any")) +
    geom_point_interactive(aes(tooltip = tooltip), 
                           size = 3, alpha = 0.5) +
    geom_line_interactive(aes(group = Symbol, tooltip = tooltip), alpha = 0.5) +
    labs(x = "Type of Participation",
         y = "Minimum Amount",
         title = "Comparison of Initial and Additional Participation Amount") +
    scale_x_continuous(breaks = c(0,10), labels = c("Initial", "Additional"),
                       expand = c(0.1,0), minor_breaks = NULL) +
    theme(plot.title = element_text(hjust = 0.5),
          panel.background = element_rect(fill = "grey95")) +
    scale_colour_manual(values = c("forestgreen", "cornflowerblue"))


ggiraph(ggobj = plot, height_svg=5, width_svg = 9, width = 1, 
        hover_css = "stroke:red; stroke-width:2pt; fill:red, fill-opacity:1")


Fund Categories

Risk Flavors

Top banks offer a mix of moderate risk and aggressive types of UITFs. BPI offers mostly aggressive-type funds, while the portfolios of both BDO and MetroBank are half aggressive and half moderate types. If you’re more inclined to conservative risk types of funds, PNB offers the most number of conservative UITFs.

plot <- UITF_matrix %>% 
    select(Bank, Risk = `Risk Classification`) %>% 
    group_by(Bank) %>% 
    mutate(Total = n()) %>% 
    ungroup() %>% 
    group_by(Bank, Risk) %>% 
    mutate(Count = n()) %>% 
    mutate(tooltip = paste(Bank,
                           paste("Risk:", Risk),
                           paste(Count, "out of", Total , "products"),
                           sep = "\n")) %>%
    ungroup() %>% 
    unique() %>% 
    arrange(desc(Total), Risk) %>%
    ggplot(aes(x = reorder(Bank, desc(Total)), 
               y = Count, 
               fill = Risk, 
               tooltip = tooltip)) +
    geom_bar_interactive(aes(data_id = "just_anything"), 
                         alpha = 0.85, stat = "identity") +
    theme(plot.title = element_text(hjust = 0.5),
          axis.title = element_blank(),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          axis.text.x = element_text(hjust=1, vjust=0.5, angle=90),
          axis.ticks.x = element_blank(),
          panel.grid = element_blank(),
          panel.background = element_rect(fill = "grey98"),
          legend.position = c(0.5, 0.85),
          legend.direction = "horizontal",
          legend.background = element_rect(color = "grey50")
    ) +
    ggtitle("Portfolio Risk Breakdown") +
    scale_fill_brewer(palette = "Set1") +
    scale_y_continuous(limits = c(0,21), expand = c(0,0))

ggiraph(ggobj = plot, height_svg=5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.5")


Classification by Investment Instruments

There are different investment instruments in which UITFs are invested in by their respective fund managers. These are either shares of stock, bonds, or money markets. The type of income instrument basically determines the risk attributed to that UITF. The table below summarizes this.

Classification: Invested in: Risk Flavor:
Equity Fund Shares of stocks Aggressive
Bond Fund Government and/or corporate bonds Moderate
Balanced Fund Mix of stocks and bonds Aggressive
Money Market Fund Short-term debt instruments Moderate/Conservative


The risk flavors for each fund classification above is not fixed. These are the typical labels used by banks when marketing their UITF products. The graph below shows how each fund classification is labelled by risk flavor according to banks.

plot <- UITF_matrix %>% 
    select(Classification = `Fund Classification`, 
           Risk = `Risk Classification`) %>% 
    mutate(Classification = factor(Classification, 
                                   levels = c("Equity Funds",
                                              "Balanced Funds",
                                              "Long Term Bond Funds",
                                              "Medium Term Bond Funds",
                                              "Intermediate Term Bond Funds",
                                              "Money Market Funds"))) %>% 
    group_by(Classification) %>% 
    mutate(Total = n()) %>% 
    ungroup() %>% 
    group_by(Classification, Risk) %>% 
    mutate(Count = n(), Ratio = n()/Total) %>% 
    ungroup() %>% 
    distinct() %>%
    mutate(tooltip = paste(Classification,
                           paste("Risk:", Risk),
                           paste(Count, "out of", Total, "products"),
                           sep = "\n")) %>% 
    arrange(as.integer(Classification), Risk) %>% 
    ggplot(aes(x = reorder(Classification, desc(Classification)), 
               y = Ratio, fill = Risk)) +
    geom_bar_interactive(aes(data_id = Risk, tooltip = tooltip),
                         stat = "identity", alpha = 0.85, position = "stack") +
    coord_flip() +
    theme(plot.title = element_text(hjust = 0.5, size = 12),
          axis.title.y = element_text(size = 9),
          axis.text.y = element_text(size = 8),
          axis.ticks.y = element_blank(),
          axis.title.x = element_text(size = 9),
          axis.text.x = element_blank(),
          axis.ticks.x = element_blank(),
          legend.position = "right",
          panel.grid.major = element_blank(),
          panel.background = element_blank()
    ) + 
    labs(title = "Risk Flavor by Fund Classification",
         x = "",
         y = "Ratio of Labels used by Banks") +
    scale_fill_brewer(palette = "Set1") +
    scale_y_continuous(expand = c(0.01,0))

ggiraph(ggobj = plot, height_svg = 3.5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65")


Performance Analysis

Top Performing UITFs

In this section we will review which UITFs performed best for the past year. Our key metric is year-over-year returns (YOY) from Apr 26, 2016 to Apr 25, 2017. The graph below shows good returns for equity funds ranging from 5.27% up to 26.79% year-over-year. Balanced funds show medium returns while money market funds have modest returns. Meanwhile, bond funds had a bad year. Intermediate-term and medium-term bond funds generally had little to zero returns while long-term bond funds are generally on the negative side.

# get returns function
get_returns <- function(symbol, price_df, YTD) {
    
    if(YTD == TRUE) {
        price_df <- price_df %>% 
            filter(year(Date) == max(year(Date)))
    }
    
    first_price <- price_df %>% 
        filter(Symbol == symbol) %>% 
        filter(Date == min(Date)) %>% 
        `[[`("NAVPU")
    
    current_price <- price_df %>% 
        filter(Symbol == symbol) %>% 
        filter(Date == max(Date)) %>% 
        `[[`("NAVPU")
    
    (current_price/first_price - 1) * 100
}

# summarize performance of each UITF
fund_performance <- UITF_matrix %>% 
    mutate(YOY = map_dbl(Symbol, get_returns, price_data, FALSE)) %>% 
    mutate(YTD = map_dbl(Symbol, get_returns, price_data, TRUE))

# display performance of all funds
plot <- fund_performance %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    ggplot(aes(x = reorder(Symbol, desc(YOY)), y = YOY, 
               fill = `Fund Classification`)) +
    geom_bar_interactive(aes(data_id = Symbol, tooltip = tooltip), 
                         stat = "identity") +
    labs(x = "UITF Symbol",
         y = "1-year return (%)",
         title = "1-Year Return of All UITFs") +
    theme(plot.title = element_text(hjust = 0.5),
          axis.text.x = element_text(size = 7, angle = 90, hjust = 1),
          axis.ticks.x = element_blank(),
          panel.grid.major.x = element_blank(),
          panel.background = element_rect(fill = "grey95"),
          legend.position = c(0.85, 0.67),
          legend.background = element_rect(color = "grey50")) +
    scale_fill_brewer(palette = "Dark2") +
    scale_y_continuous(limits = c(-7,28), breaks = seq(-5,25,5))

ggiraph(ggobj = plot, height_svg = 4.5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65")


Different date range yields different returns. Another common calculation used is the year-to-date (YTD) return. This calculates the returns with reference to the prices at the start of the year. The graph of YTD returns below shows that almost all funds have positive returns. Still, equity funds had the largest gains followed by balanced funds, bond funds, and money market funds respectively.

# display performance of all funds
plot <- fund_performance %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    ggplot(aes(x = reorder(Symbol, desc(YTD)), y = YTD, 
               fill = `Fund Classification`)) +
    geom_bar_interactive(aes(data_id = Symbol, tooltip = tooltip), 
                         stat = "identity") +
    labs(x = "UITF Symbol",
         y = "YTD return (%)",
         title = "Year-to-Date Return of All UITFs") +
    theme(plot.title = element_text(hjust = 0.5),
          axis.text.x = element_text(size = 7, angle = 90, hjust = 1),
          axis.ticks.x = element_blank(),
          panel.grid.major.x = element_blank(),
          panel.background = element_rect(fill = "grey95"),
          legend.position = c(0.85, 0.67),
          legend.background = element_rect(color = "grey50")) +
    scale_fill_brewer(palette = "Dark2") +
    scale_y_continuous(limits = c(-7,28), breaks = seq(-5,25,5))

ggiraph(ggobj = plot, height_svg = 4.5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65")


Performance by Fund Classification

Money Market Funds

Chinabank UITFs had the best returns for money market funds, followed by PNB and BDO. Meanwhile, MetroBank money market funds had the lowest gains year-over-year.

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Money Market Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         title = "Money Market Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
    

ggiraph(ggobj = plot, height_svg = 5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Medium Term Bond Funds

No consistency for medium term bond funds as seen in the graph below. Some funds have negative YOY returns but positive YTD returns. But most of them are positive since the start of the year.

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Medium Term Bond Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         title = "Medium Term Bond Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
    

ggiraph(ggobj = plot, height_svg = 3, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Intermediate Term Bond Funds

All have positive YTD returns except for UnionBank Infinity Prime Bond. Top performing fund year-over-year is China Bank’s Intermediate Fixed Income Fund.

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Intermediate Term Bond Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         title = "Intermediate Term Bond Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
ggiraph(ggobj = plot, height_svg = 3, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Long Term Bond Funds

More than half of these funds are negative year-over-year but all have been positive year-to-date. Top performing funds are from ATRAM.

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Long Term Bond Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         title = "Long Term Bond Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
    

ggiraph(ggobj = plot, height_svg = 4.5, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Balanced Funds

More than half of these funds are negative year-over-year but all have been positive year-to-date. Top performing funds are from ATRAM.

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Balanced Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         y = "Percent Return",
         title = "Balanced Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
    

ggiraph(ggobj = plot, height_svg = 3, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Equity Funds

As seen previously, equity funds yielded good returns both YOY and YTD. All funds have positive YOY and YTD returns. Top performing funds are from ATRAM, BDO, and BPI respectively

plot <- fund_performance %>% 
    filter(`Fund Classification` == "Equity Funds") %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           paste0("YOY Return: ", 
                                  formatC(YOY, format="f", digits=2), "%"),
                           paste0("YTD Return: ", 
                                  formatC(YTD, format="f", digits=2), "%"),
                           sep = "\n")) %>% 
    arrange(Bank, desc(YOY)) %>% 
    mutate(order = 1:n()) %>% 
    ggplot(aes(y = YOY, x = reorder(Symbol, desc(order)), fill = Bank,
               label = paste0(formatC(YOY, format = "f", digits = 2), "%"),
               tooltip = tooltip, data_id = Symbol)) +
    geom_bar_interactive(stat = "identity", width = 0.9, alpha = 0.6) +
    geom_bar_interactive(stat = "identity", width = 0.4, alpha = 1, 
                         aes(y = YTD)) +
    scale_fill_manual(values = my_qual_palette(length(unique(UITF_matrix$Bank)))) +
    labs(x = "UITF Symbol",
         y = "Percent Return",
         title = "Equity Funds - YTD and YOY Returns",
         subtitle = "YTD: thin bars | YOY: thick bars") + 
    coord_flip() +
    scale_y_continuous(expand = c(0.01,0.03)) +
    theme(panel.background = element_blank(),
          panel.grid.major.x = element_line(color = "grey95"),
          panel.grid.minor.x = element_line(color = "grey95"),
          axis.ticks.y = element_blank(),
          plot.title = element_text(hjust = 0.5),
          plot.subtitle = element_text(hjust = 0.5),
          legend.background = element_rect(color = "grey50"))
    
    

ggiraph(ggobj = plot, height_svg = 8, width_svg = 9, width = 1,
        hover_css = "fill-opacity:0.65; stroke:grey99")


Browse through the interactive table below to have another look at our key performance metrics.

# display interactive table
fund_performance %>% 
    arrange(desc(YOY)) %>% 
    mutate(YOY = round(YOY, digits = 2),
           YTD = round(YTD, digits = 2)) %>% 
    select(Bank, Symbol, Classification = `Fund Classification`, YOY, YTD) %>% 
    mutate(Bank = factor(Bank), Classification = factor(Classification)) %>% 
    datatable(filter = "top", 
              class = 'stripe',
              options = list(dom = "pt",
                             pagelength = 20,
                             autoWidth = TRUE
                             )
              )    


Risk Analysis

Looking at YTD and YOY returns does not tell the entire story. Equity funds may have yielded the best returns among all types of funds. However, looking at the graph below, your returns depend a lot on your entry and exit points, especially with equity funds which took a 5-month plunge and has since only recovered. Balanced funds tell almost the same story but the relative price movements are much smaller in comparison. On the other end, money market funds look pretty much a straight line with a very shallow slope.

These price movements are what defines which funds are likely to be aggressive, of moderate risk, or conservative.

plot <- returns_data %>% 
    left_join(UITF_matrix[, c("Symbol", "Name", "Bank", "Fund Classification")]) %>% 
    mutate(tooltip = paste(Name,
                           paste("Symbol:", Symbol),
                           paste("Bank:", Bank),
                           paste("Classification:", `Fund Classification`),
                           sep = "\n")) %>% 
    ggplot(aes(x = Date, y = Return, group = Symbol,
               col = `Fund Classification`)) +
    geom_line(alpha = 0.4) +
    labs(title = "Daily Percent Returns of all UITFs") +
    scale_colour_brewer(palette = "Dark2") +
    theme(legend.position = "bottom",
          plot.title = element_text(hjust = 0.5),
          panel.background = element_rect(fill = "grey95"))

ggiraph(ggobj = plot, height_svg = 5, width_svg = 9, width = 1,
        hover_css = "stroke:black; stroke-width:2")


Another way to look at the volatility of prices is the daily percent changes. Based from the graph below, key observations are:

  1. Equity funds have a wild variation between daily prices.
  2. Money market funds have the smallest swings among all classifications.
  3. Intermediate-term and medium-term bond funds look very similar.
plot <- returns_data %>% 
    left_join(UITF_matrix[, c("Symbol", "Fund Classification")]) %>% 
    mutate(`Fund Classification` = factor(`Fund Classification`,
                                          levels = c("Equity Funds",
                                                     "Balanced Funds",
                                                     "Long Term Bond Funds",
                                                     "Intermediate Term Bond Funds",
                                                     "Medium Term Bond Funds",
                                                     "Money Market Funds"))) %>% 
    ggplot(aes(x = Date, y = Change, group = Symbol,
               col = `Fund Classification`)) +
    geom_line(alpha = 0.4) +
    facet_grid(. ~ `Fund Classification`) +
    labs(title = "Daily Percent Returns of all UITFs") +
    scale_colour_brewer(palette = "Dark2") +
    theme(legend.position = "bottom",
          plot.title = element_text(hjust = 0.5),
          axis.text.x = element_text(angle = 90),
          panel.background = element_rect(fill = "grey95"))

ggiraph(ggobj = plot, height_svg = 5, width_svg = 9, width = 1,
        hover_css = "stroke:black; stroke-width:2")


Another way to look at the above comparison is through the density plots below for each UITF separated by class. The relative size of daily price changes is again obvious across different fund classifications. An interesting observation here is that all distributions seem to be centered at zero except for money market funds whose means are a little above zero. This tells a lot how money market funds are considered conservative; low gains but very little risk involved

plot <- returns_data %>% 
    left_join(UITF_matrix[, c("Symbol", "Fund Classification")]) %>% 
    mutate(`Fund Classification` = factor(`Fund Classification`,
                                          levels = c("Equity Funds",
                                                     "Balanced Funds",
                                                     "Long Term Bond Funds",
                                                     "Intermediate Term Bond Funds",
                                                     "Medium Term Bond Funds",
                                                     "Money Market Funds"))) %>% 
    ggplot(aes(x = Change, fill = `Fund Classification`, group = Symbol)) +
    geom_density(aes(y = ..scaled..), col = NA, alpha = 0.2) +
    geom_vline(xintercept = 0, size = 0.1) +
    labs(title = "Density Estimate of Daily Percent Changes per UITF") +
    scale_colour_brewer(palette = "Dark2") +
    scale_x_continuous(limits = c(-3, 3), breaks = seq(-5,5,1)) +
    facet_wrap(~`Fund Classification`, ncol = 1, strip.position = "top") +
    labs(y = "", 
         x = "Percent Change") +
    theme(plot.title = element_text(hjust = 0.5),
          axis.text.y = element_blank(),
          axis.ticks.y = element_blank(),
          panel.background = element_rect(fill = "grey95"),
          strip.text.x = element_blank()
          )
    

ggiraph(ggobj = plot, height_svg = 5, width_svg = 9, width = 1,
        hover_css = "stroke:black; stroke-width:2")


Summary

In this simple analysis we were able to gather information about all active UITFs in the Philippines. We were able to accomplish the following:

  1. We gathered information on all active UITFs in the Philippines and extracted important insights such as:
    1. The earliest UITFs were around since 1994.
    2. There are 122 currently active UITFs either in peso or US dollar denominations offered by 15 different banks.
    3. The typical minimum initial participation is P10,000 but can be as low as P1,000 for other funds.
    4. There are 6 different fund classifications based on where the funds are invested in; each carrying a different level of risk categorized as one of conservative, moderate, or aggressive.
  2. We identified the top performing UITFs overall and by fund classification and discovered that:
    1. Equity funds have the highest returns both YTD and YOY
    2. Majority of long term bond funds are on the negative.
    3. All money market funds have low but positive returns.
  3. We compared the different fund classifications in terms of performance as well as the risk involved by visualizing percent returns and price changes and came up with the following insights:
    1. Equity funds have the highest price volatility among all classes
    2. Money market funds have the least variation.
    3. Money market funds have very low daily price changes but are almost always positive.

Monitoring prices on a daily basis will surely give a scare especially for the large price movements for equity funds. However, daily percent changes do not capture the overall trend. This is the reason why the distribution of daily percent changes for almost all UITFs are centered at zero but the YOY returns are mostly positive. It is important to look at the overall trend whether you’re looking to maximize gains or preserve your capital. After all, UITFs are generally long term investment instruments.

Finally, the performance comparisons done here are true only for the subset of price history for May 2, 2016 to Apr 27, 2017. It will be good to gather more data for a longer date range extending to years prior to better generalize our observations and also to monitor future prices and be able to redo our analysis.